package com.zx.zxapiorder.service.impl;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zx.zxapicommon.common.BaseResponse;
import com.zx.zxapicommon.constant.CookieConstant;
import com.zx.zxapicommon.constant.OrderConstant;
import com.zx.zxapicommon.exception.BusinessException;
import com.zx.zxapicommon.model.dto.ApiOrderCancelDto;
import com.zx.zxapicommon.model.entity.InterfaceInfo;
import com.zx.zxapicommon.model.enums.ErrorCode;
import com.zx.zxapicommon.model.to.GetAvailablePiecesTo;
import com.zx.zxapicommon.model.vo.LockChargingVo;
import com.zx.zxapicommon.model.vo.LoginUserVO;
import com.zx.zxapicommon.model.vo.OrderInterfaceInfoVo;
import com.zx.zxapicommon.utils.ResultUtils;
import com.zx.zxapiorder.feign.UserFeignServices;
import com.zx.zxapiorder.mapper.ApiOrderMapper;
import com.zx.zxapiorder.model.dto.ApiOrderDto;
import com.zx.zxapiorder.model.entity.ApiOrder;
import com.zx.zxapiorder.model.entity.ApiOrderLock;
import com.zx.zxapiorder.model.to.ApiOrderStatusInfoDto;
import com.zx.zxapiorder.model.vo.ApiOrderStatusVo;
import com.zx.zxapiorder.model.vo.OrderSnVo;
import com.zx.zxapiorder.service.ApiOrderLockService;
import com.zx.zxapiorder.service.ApiOrderService;
import com.zx.zxapiorder.utils.RabbitOrderUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * @author Bro
 * @description 针对表【api_order】的数据库操作Service实现
 * @createDate 2023-07-16 12:58:04
 */
@Service
public class ApiOrderServiceImpl extends ServiceImpl<ApiOrderMapper, ApiOrder>
        implements ApiOrderService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Resource
    private RabbitOrderUtils rabbitOrderUtils;

    @Resource
    private UserFeignServices userFeignServices;

    @Resource
    private ThreadPoolExecutor executor;

    @Resource
    private ApiOrderLockService apiOrderLockService;

    @Resource
    private ApiOrderMapper apiOrderMapper;

    /**
     * 生成订单
     *
     * @param apiOrderDto
     * @param request
     * @param response
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public BaseResponse<OrderSnVo> generateOrderSn(@RequestBody ApiOrderDto apiOrderDto, HttpServletRequest request, HttpServletResponse response) throws ExecutionException, InterruptedException {

        // todo 解决方法，异步调用丢失了请求头，从RequestContextHolder获取即可
        //BaseResponse baseResponse = userFeignServices.checkUserLogin(request,response);
        //BaseResponse baseResponse = userFeignServices.checkLogin(request,response);
        //其他类型的请求都需要判断用户是否登录
        //校验用户是否登录
        String cookie1 = request.getHeader("Cookie");
        String loginUserVo = HttpRequest.post("http://117.72.8.188:8082/api/user/checkUserLogin")
                .header("Cookie", cookie1)
                .timeout(20000)
                .execute().body();

        JSONObject entrie = JSONUtil.parseObj(loginUserVo);
        BaseResponse baseResponse = JSONUtil.toBean(entrie, BaseResponse.class);

        LoginUserVO loginUserVO = JSONUtil.toBean(JSONUtil.parseObj(baseResponse.getData()), LoginUserVO.class);
        if (null == loginUserVO) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        //2.参数校验
        Long interfaceId = apiOrderDto.getInterfaceId();
        Long userId = apiOrderDto.getUserId();
        Long orderNum = apiOrderDto.getOrderNum();
        Double charging = apiOrderDto.getCharging();
        Double totalAmount = apiOrderDto.getTotalAmount();
        if (null == interfaceId || null == userId || null == orderNum || null == charging || null == totalAmount) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        if (!userId.equals(loginUserVO.getId())) {
            throw new BusinessException(ErrorCode.FORBIDDEN_ERROR);
        }
        //保留两位小数点
        double tempAmount = orderNum * charging;
        BigDecimal two = new BigDecimal(tempAmount);
        Double three = two.setScale(2, BigDecimal.ROUND_HALF_DOWN).doubleValue();
        if (!three.equals(totalAmount)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR);
        }

        //3.验证令牌是否合法(令牌的对比和删除必须保证原子性)
        Cookie[] cookies = request.getCookies();
        String token = null;
        //从cookie取出token
        for (Cookie cookie : cookies) {
            if (CookieConstant.orderToken.equals(cookie.getName())) {
                token = cookie.getValue();
            }
        }

        //lua脚本
        String script =
                "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";

        Long result = (Long) redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
                Collections.singletonList(OrderConstant.USER_ORDER_TOKEN_PREFIX + loginUserVO.getId()),
                token);

        if (result == 0L) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "提交太快了，请重新提交");
        }

        //4.远程查询是否还有库存
        GetAvailablePiecesTo getAvailablePiecesTo = new GetAvailablePiecesTo();
        getAvailablePiecesTo.setInterfaceId(interfaceId);
        BaseResponse presentAvailablePieces = userFeignServices.getPresentAvailablePieces(getAvailablePiecesTo);
        String availablePiecesData = (String) presentAvailablePieces.getData();

        if (Integer.parseInt(availablePiecesData) < orderNum) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "库存不足，请刷新页面,当前剩余库存为：" + availablePiecesData);
        }

        //5.远程异步调用查询接口信息
        //拿到主线程中的请求头，解决异步请求丢失请求头的问题
        //开启子线程异步进行订单接口信息的查询
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        OrderInterfaceInfoVo orderInterfaceInfoVo = new OrderInterfaceInfoVo();

        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
            //异步查询
            RequestContextHolder.setRequestAttributes(requestAttributes);
            BaseResponse orderInterfaceInfo = userFeignServices.getOrderInterfaceInfo(getAvailablePiecesTo);
            JSONObject entries = JSONUtil.parseObj(orderInterfaceInfo.getData());
            InterfaceInfo interfaceInfo = JSONUtil.toBean(entries, InterfaceInfo.class);
            orderInterfaceInfoVo.setName(interfaceInfo.getName());
            orderInterfaceInfoVo.setDescription(interfaceInfo.getDescription());
        }, executor);

        //6.使用雪花算法生成订单id，并保存订单
        String orderSn = generateOrderSn(loginUserVO.getId().toString());
        ApiOrder apiOrder = new ApiOrder();
        apiOrder.setInterfaceId(interfaceId);
        apiOrder.setUserId(userId);
        apiOrder.setOrderSn(orderSn);
        apiOrder.setOrderNum(orderNum);
        apiOrder.setCharging(charging);
        apiOrder.setTotalAmount(totalAmount);
        apiOrder.setStatus(OrderConstant.toBePaid);
        try {
            apiOrderMapper.insert(apiOrder);
        } catch (Exception e) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "订单保存失败");
        }

        //7.锁定剩余库存
        ApiOrderLock apiOrderLock = new ApiOrderLock();
        apiOrderLock.setUserId(userId);
        apiOrderLock.setLockNum(orderNum);
        apiOrderLock.setLockStatus(1);
        apiOrderLock.setChargingId(interfaceId);
        apiOrderLock.setOrderSn(orderSn);
        try {
            apiOrderLockService.save(apiOrderLock);
        } catch (Exception e) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "库存锁定失败");
        }

        //8.远程更新剩余可调用接口数量
        LockChargingVo lockChargingVo = new LockChargingVo();
        lockChargingVo.setInterfaceId(interfaceId);
        lockChargingVo.setOrderNum(orderNum);
        BaseResponse updateAvailablePieces = userFeignServices.updateAvailablePieces(lockChargingVo);
        if (updateAvailablePieces.getCode() != 0) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "库存更新失败");
        }

        //9.全部锁定完成后，向mq延时队列发送订单消息，过期时间30分钟
        rabbitOrderUtils.sendOrderSnInfo(apiOrder);
        //等待异步任务完成
        CompletableFuture.allOf(voidCompletableFuture).get();
        //10、构建返回给前端页面的数据
        OrderSnVo orderSnVo = new OrderSnVo();
        BeanUtils.copyProperties(apiOrder, orderSnVo);
        DateTime date = DateUtil.date();
        orderSnVo.setCreateTime(date);
        orderSnVo.setExpirationTime(DateUtil.offset(date, DateField.MINUTE, 30));
        orderSnVo.setName(orderInterfaceInfoVo.getName());
        orderSnVo.setDescription(orderInterfaceInfoVo.getDescription());
        return ResultUtils.success(orderSnVo);
    }


    /**
     * 生成防重令牌：保证创建订单的接口幂等性
     *
     * @param id
     * @param response
     * @return
     */
    @Override
    public BaseResponse generateToken(Long id, HttpServletResponse response) {
        if (null == id) {
            throw new BusinessException(ErrorCode.FORBIDDEN_ERROR);
        }
        //防重令牌
        String token = IdUtil.simpleUUID();
        redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX + id, token, 30, TimeUnit.MINUTES);
        Cookie cookie = new Cookie(CookieConstant.orderToken, token);
        cookie.setPath("/");
        cookie.setMaxAge(CookieConstant.orderTokenExpireTime);
        response.addCookie(cookie);
        return ResultUtils.success(null);
    }

    /**
     * 获取当前登录用户的status订单信息
     *
     * @param statusInfoDto
     * @param request
     * @return
     */
    @Override
    public BaseResponse<Page<ApiOrderStatusVo>> getCurrentOrderInfo(ApiOrderStatusInfoDto statusInfoDto, HttpServletRequest request) {
        Long userId = statusInfoDto.getUserId();
        //前端筛选即可
        Integer status = statusInfoDto.getStatus();
        if (null == userId) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        long current = statusInfoDto.getCurrent();
        // 限制爬虫
        long size = statusInfoDto.getPageSize();
        if (size > 20) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Page<ApiOrderStatusVo> apiOrderStatusVo = apiOrderMapper.getCurrentOrderInfo(new Page<>(current, size), userId, status);
        List<ApiOrderStatusVo> records = apiOrderStatusVo.getRecords();
        List<ApiOrderStatusVo> collect = records.stream().map(record -> {
            Integer status1 = record.getStatus();
            if (status1 == 0) {
                Date date = record.getCreateTime();
                record.setExpirationTime(DateUtil.offset(date, DateField.MINUTE, 30));
            }
            return record;
        }).collect(Collectors.toList());
        apiOrderStatusVo.setRecords(collect);
        return ResultUtils.success(apiOrderStatusVo);
    }



    /**
     * 取消订单
     *
     * @param apiOrderCancelDto
     * @param request
     * @param response
     * @return
     */
    @Override
    @Transactional
    public BaseResponse cancelOrderSn(ApiOrderCancelDto apiOrderCancelDto, HttpServletRequest request, HttpServletResponse response) {
        String orderSn = apiOrderCancelDto.getOrderSn();
        Long orderNum = apiOrderCancelDto.getOrderNum();
        //订单已经被取消的情况,保证幂等性
        ApiOrder order = this.getOne(new QueryWrapper<ApiOrder>().eq("orderSn", orderSn));
        if (order.getStatus() == 2) {
            return ResultUtils.success("取消订单成功~");
        }
        //更新库存锁定表
        apiOrderLockService.update(new UpdateWrapper<ApiOrderLock>().eq("orderSn", orderSn).set("lockStatus", 0));
        //更新订单表状态
        this.update(new UpdateWrapper<ApiOrder>().eq("orderSn", orderSn).set("status", 2));
        //远程调用 - 解锁库存
        LockChargingVo lockChargingVo = new LockChargingVo();
        lockChargingVo.setOrderNum(orderNum);
        lockChargingVo.setInterfaceId(apiOrderCancelDto.getInterfaceId());
        BaseResponse res = userFeignServices.unlockAvailablePieces(lockChargingVo);
        if (res.getCode() != 0) {
            throw new RuntimeException();
        }
        return ResultUtils.success("取消订单成功~");
    }

    @Override
    public void orderPaySuccess(String orderSn) {
        this.update(new UpdateWrapper<ApiOrder>().eq("orderSn", orderSn).set("status", 1));
    }


    /**
     * 生成订单号
     *
     * @return
     */
    private String generateOrderSn(String userId) {
        String timeId = IdWorker.getTimeId();
        String substring = timeId.substring(0, timeId.length() - 15);
        return substring + RandomUtil.randomNumbers(5) + userId.substring(userId.length() - 2, userId.length());
    }
}




